#include <dirent.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <ctype.h>
#include "esp_check.h"
#include "esp_log.h"
#include "file_iterator.h"

// 文件类型的扩展名
static const char* audio_exts[] = {".mp3", ".wav", /*".aac", ".flac", ".ogg",*/ NULL};
static const char* video_exts[] = {".mp4", /*".avi", ".mkv", ".mov",*/ NULL};
static const char* image_exts[] = {".jpg", ".png", /*".jpeg", ".bmp", ".gif",*/ NULL};

#define TAG "FILE_ITERATOR"

/**
 * @brief 检查文件名是否匹配任一扩展名
 * @param filename 要检查的文件名
 * @param exts 扩展名数组(以NULL结尾)
 * @return 如果匹配返回真，否则返回假
 */
static bool match_extension(const char* filename, const char** exts) 
{
    if (!filename || !exts) 
        return false;
    
    size_t len = strlen(filename);
    for (const char** ext = exts; *ext; ext++) {
        size_t ext_len = strlen(*ext);
        if (len >= ext_len && 
            strcasecmp(filename + len - ext_len, *ext) == 0) {
            return true;
        }
    }
    return false;
}

/**
 * @brief 检查文件名是否匹配自定义扩展名
 * @param filename 要检查的文件名
 * @param custom_exts 逗号分隔的扩展名(例如".txt,.log")
 * @return 如果匹配返回真，否则返回假
 */
static bool match_custom_extension(const char* filename, const char* custom_exts) {
    if (!filename || !custom_exts) return false;
    
    char* exts = strdup(custom_exts);
    char* saveptr = NULL;
    char* ext = strtok_r(exts, ",", &saveptr);
    
    bool matched = false;
    size_t len = strlen(filename);
    
    while (ext) {
        // Trim whitespace
        while (isspace((unsigned char)*ext)) ext++;
        
        size_t ext_len = strlen(ext);
        if (len >= ext_len && 
            strcasecmp(filename + len - ext_len, ext) == 0) {
            matched = true;
            break;
        }
        ext = strtok_r(NULL, ",", &saveptr);
    }
    
    free(exts);
    return matched;
}

/**
 * @brief 创建新的文件迭代器实例，可指定文件类型过滤器
 * @param base_path 要扫描的目录路径
 * @param type 要过滤的文件类型
 * @param custom_exts 自定义扩展名(仅当type为FILE_TYPE_CUSTOM时使用)
 * @return 返回新文件迭代器实例指针，失败时返回NULL
 */
static esp_err_t file_scan(file_iterator_instance_t *i, const char *base_path, 
                          file_type_t type, const char* custom_exts)
{
    i->count = 0;
    struct dirent *p_dirent = NULL;
    DIR *p_dir_stream = opendir(base_path);

    // First pass: count matching files
    while ((p_dirent = readdir(p_dir_stream)) != NULL) {
        if (p_dirent->d_type != DT_REG) continue; // Skip non-regular files
        
        bool include = true;
        if (type != FILE_TYPE_ALL) {
            include = false;
            switch (type) {
                case FILE_TYPE_AUDIO:
                    include = match_extension(p_dirent->d_name, audio_exts);
                    break;
                case FILE_TYPE_VIDEO:
                    include = match_extension(p_dirent->d_name, video_exts);
                    break;
                case FILE_TYPE_IMAGE:
                    include = match_extension(p_dirent->d_name, image_exts);
                    break;
                case FILE_TYPE_CUSTOM:
                    include = match_custom_extension(p_dirent->d_name, custom_exts);
                    break;
                default:
                    break;
            }
        }
        
        if (include) {
            i->count++;
        }
    }
    closedir(p_dir_stream);
    if (i->count == 0) {
        ESP_LOGI(TAG, "No files found in directory %s", base_path);
        return ESP_OK;
    }

    i->list = (char **) malloc(i->count * sizeof(char *));
    ESP_RETURN_ON_FALSE(NULL != i->list, ESP_ERR_NO_MEM,
        TAG, "Failed allocate audio list buffer");

    // Second pass: store matching filenames
    p_dir_stream = opendir(base_path);
    size_t file_index = 0;
    while ((p_dirent = readdir(p_dir_stream)) != NULL && file_index < i->count) {
        if (p_dirent->d_type != DT_REG) continue; // Skip non-regular files
        
        bool include = true;
        if (type != FILE_TYPE_ALL) {
            include = false;
            switch (type) {
                case FILE_TYPE_AUDIO:
                    include = match_extension(p_dirent->d_name, audio_exts);
                    break;
                case FILE_TYPE_VIDEO:
                    include = match_extension(p_dirent->d_name, video_exts);
                    break;
                case FILE_TYPE_IMAGE:
                    include = match_extension(p_dirent->d_name, image_exts);
                    break;
                case FILE_TYPE_CUSTOM:
                    include = match_custom_extension(p_dirent->d_name, custom_exts);
                    break;
                default:
                    break;
            }
        }
        
        if (include) {
            i->list[file_index] = strdup(p_dirent->d_name);
            ESP_LOGI(TAG, "File : %s", i->list[file_index]);
            file_index++;
        }
    }

    closedir(p_dir_stream);
    return ESP_OK;
}

size_t file_iterator_get_count(file_iterator_instance_t *i) {
    return i->count;
}

size_t file_iterator_get_index(file_iterator_instance_t *i)
{
    ESP_LOGI(TAG, "file_iterator_get_index() %d", i->index);
    return i->index;
}

void file_iterator_set_index(file_iterator_instance_t *i, size_t index)
{
    ESP_LOGI(TAG, "file_iterator_set_index() %d", index);

    if(index >= i->count) {
        index = 0;
    }

    i->index = index;
}

const char* file_iterator_get_name_from_index(file_iterator_instance_t *i, size_t index)
{
    ESP_RETURN_ON_FALSE(index < i->count, NULL,
        TAG, "File index out of range");

    ESP_RETURN_ON_FALSE(NULL != i->list, NULL,
        TAG, "File not found");

    ESP_RETURN_ON_FALSE(NULL != i->list[index], NULL,
        TAG, "File not found");

    return i->list[index];
}

int file_iterator_get_full_path_from_index(file_iterator_instance_t* i, size_t index, char* path, size_t path_len)
{
    const char* name = file_iterator_get_name_from_index(i, index);
    if(!name) {
        return 0;
    }

    int retval = snprintf(path, path_len, "%s/%s", i->directory_path, name);

    return retval;
}

esp_err_t file_iterator_next(file_iterator_instance_t *i)
{
    i->index++;
    if (i->index >= i->count) {
        i->index = 0;
    }
    return ESP_OK;
}

esp_err_t file_iterator_prev(file_iterator_instance_t *i)
{
    if (i->index == 0) {
        i->index = i->count;
    }
    i->index--;
    return ESP_OK;
}

file_iterator_instance_t* file_iterator_new(
    const char *base_path, file_type_t type, const char *custom_exts) 
{
    file_iterator_instance_t *i = NULL;
    esp_err_t ret;

    /* Scan files */
    if (NULL != base_path) {
        i = malloc(sizeof(file_iterator_instance_t));
        i->index = 0;
        ret = file_scan(i, base_path, type, custom_exts);
        if(ret == ESP_OK) {
            i->directory_path = strdup(base_path);
        } else {
            free(i);
            i = NULL;
        }
    } else {
        ESP_LOGE(TAG, "Invalid base path");
    }

    return i;
}

void file_iterator_delete(file_iterator_instance_t *i)
{
    if (i == NULL) {
        return;
    }

    ESP_LOGI(TAG, "Deleting file iterator instance");

    // Free all file name strings
    if (i->list != NULL) {
        for (size_t idx = 0; idx < i->count; idx++) {
            if (i->list[idx] != NULL) {
                free(i->list[idx]);
                i->list[idx] = NULL;
            }
        }
        free(i->list);
        i->list = NULL;
    }

    // Free directory path string
    if (i->directory_path != NULL) {
        free((void *)i->directory_path);
        i->directory_path = NULL;
    }

    // Free instance itself
    free(i);
}
